#!/usr/bin/env ruby

## The MIT License (MIT)
##
## Copyright (c) 2018 SavinMax. All rights reserved.
##
## Permission is hereby granted, free of charge, to any person obtaining a copy
## of this software and associated documentation files (the "Software"), to deal
## in the Software without restriction, including without limitation the rights
## to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
## copies of the Software, and to permit persons to whom the Software is
## furnished to do so, subject to the following conditions:
##
## The above copyright notice and this permission notice shall be included in
## all copies or substantial portions of the Software.
##
## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
## IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
## FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
## AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
## LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
## OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
## THE SOFTWARE.

require "yaml"
require "./tools/utils"

defines = Utils.all_structs
msg_defines = Utils.msg_structs

COMMON_TYPE = ['bool', 'byte', 'string', 
               'uint16', 'uint24', 'uint32', 'uint64', 
               'int16', 'int24', 'int32', 'int64',
               'float32', 'float64']
PROTOCOL_TYPE = defines.keys

ERR_RETURN = "if err != nil {return nil, err}"

package = "rawapi"
proto_dir = "../src/goslib/gen/api/#{package}"
`mkdir -p #{proto_dir}`

#
# Generate api decoder
#
target = "#{proto_dir}/decoder.go"

def common_protocol(field_type, variable, code)
  if code == "write"
    "buffer.Write#{field_type.capitalize}(v.#{variable})"
  elsif code == "read"
    %Q{data.#{variable}, err = buffer.Read#{field_type.capitalize}() 
       #{ERR_RETURN}}
  else
    assert(false, "Invalid code!")
  end
end

header = %Q{\
/*
 * Generated by tools/gen_protocol_raw
 */
}

encode_handlers = []
decode_handlers = []

defines.each do |protocol, define|
  encode_fields = []
  decode_fields = []
  define.each do |field, type|
    if COMMON_TYPE.include?(type)
      decode_fields << common_protocol(type, field, "read")
      encode_fields << common_protocol(type, field, "write")
    elsif PROTOCOL_TYPE.include?(type)
      decode_fields << %Q{
        value#{field}, err := decode#{type}(buffer) 
        #{ERR_RETURN}
        data.#{field} = value#{field}.(*pt.#{type})
      }
      encode_fields << %Q{encode#{type}(buffer, v.#{field})}
    elsif type.index("array-") == 0
      sub_type = type.split('-')[1]
      if COMMON_TYPE.include?(sub_type)
        decode_fields << %Q{
    #{field}Len, err := buffer.ReadUint16()
    #{ERR_RETURN}
    for i := 0; i < int(#{field}Len); i++ {
        elem, err := buffer.Read#{sub_type.capitalize}()
        #{ERR_RETURN}
        data.#{field} = append(data.#{field}, elem)
    }}
        encode_fields << %Q{
    buffer.WriteUint16(uint16(len(v.#{field})))
    for i := 0; i < len(v.#{field}); i++ {
        buffer.Write#{sub_type.capitalize}(v.#{field}[i])
    }}
      else
        decode_fields << %Q{
          #{field}Len, err := buffer.ReadUint16()
          #{ERR_RETURN}
          for i := 0; i < int(#{field}Len); i++ {
              elem, err := decode#{sub_type}(buffer)
              #{ERR_RETURN}
              data.#{field} = append(data.#{field}, elem.(*pt.#{sub_type}))
          }
        }
        encode_fields << %Q{
          buffer.WriteUint16(uint16(len(v.#{field})))
          for i := 0; i < len(v.#{field}); i++ {
              encode#{sub_type}(buffer, v.#{field}[i])
          }
        }
      end
    end
  end
  decode_handlers << %Q{
func decode#{protocol}(buffer *packet.Packet) (interface{}, error) {
    var err error
    data := &pt.#{protocol}{}
    #{decode_fields.join("\n    ")}
    return data, err
}}
  encode_handlers << %Q{
func encode#{protocol}(buffer *packet.Packet, value interface{}) {
    v := value.(*pt.#{protocol})
    #{encode_fields.join("\n    ")}
}}
end

#
# Generate api decoder
#
File.open(target, "w") do |io|
  io.write %Q{\
#{header}
package #{package}

import (
    "errors"
    "github.com/mafei198/gos/goslib/gen/api/pt"
    "github.com/mafei198/gos/goslib/packet"
)

#{decode_handlers.join("\n")}

type DecodeHandler func(buffer *packet.Packet) (interface{}, error)

var decode_handlers = map[string]DecodeHandler{
    #{defines.keys.map{|protocol| "\"#{protocol}\": decode#{protocol}"}.join(",\n    ")}}

func Decode(decode_method string, buffer *packet.Packet) (interface{}, error) {
    if handler, ok := decode_handlers[decode_method]; ok {
        return handler(buffer)
    } else {
        return nil, errors.New("decode handler not found")
    }
}
}
end
`gofmt -w #{target}`

#
# Generate api encoder
#
target = "#{proto_dir}/encoder.go"
File.open(target, "w") do |io|
  io.write %Q{\
#{header}
package #{package}

import (
	"fmt"
	"reflect"
	"errors"
    "github.com/mafei198/gos/goslib/gen/api/pt"
  	"github.com/mafei198/gos/goslib/packet"
)

#{encode_handlers.join("\n")}

type EncodeHandler func(buffer *packet.Packet, value interface{})
func Encode(v interface{}) (*packet.Packet, error) {
	encode_method, handler := EncodeMethod(v)
    if handler == nil {
        errMsg := fmt.Sprintf("no encode handle: %s", reflect.TypeOf(v).String())
		return nil, errors.New(errMsg)
    }
	protocol := pt.NameToId[encode_method]
	buffer := packet.Writer()
	buffer.WriteUint16(protocol)
	handler(buffer, v)
    return buffer, nil
}
func EncodeMethod(v interface{}) (string, EncodeHandler) {
	switch v.(type) {
    #{msg_defines.keys.map{|protocol| "case *pt.#{protocol}: return pt.PT_#{protocol}, encode#{protocol}"}.join("\n    ")}
	}
	return "", nil
}
}
end
`gofmt -w #{target}`
